#pragma once

#include <iostream>
#include <queue>
#include <pthread.h>

namespace ns_blockqueue
{
    const int default_cap = 5;
    template <class T>
    class BlockQueue
    {
    private:
        std::queue<T> _bq;    //阻塞队列
        int _cap;             //队列容量
        pthread_mutex_t _mtx; //保护临界资源的锁
        // 1、当生产满的时候就不要生产了(不要竞争锁)，而应该让消费者来消费
        // 2、当消费空了，就不要消费了(不要竞争锁)，应该让生产者来生产
        pthread_cond_t is_full;  // bq满了，生产者在该条件变量下等待
        pthread_cond_t is_empty; // bq空了，消费者在该条件变量下等待
    private:
        bool IsFull()
        {
            //当队列有效数据个数等于容量就说明满了
            return _bq.size() == _cap;
        }
        bool IsEmpty()
        {
            //数据为0就说明是空队列
            return _bq.size() == 0;
        }
        void LockQueue()
        {
            pthread_mutex_lock(&_mtx);
        }
        void UnlockQueue()
        {
            pthread_mutex_unlock(&_mtx);
        }
        void ProducterWait()
        {
            //由于队列满了，让生产者在is_full条件变量下的等待
            // 1、调用这个函数时会自动释放_mtx，然后再挂起自己
            // 2、返回的时候，会首先自动竞争锁，获取到锁之后才能返回
            pthread_cond_wait(&is_full, &_mtx);
        }
        void ConsumerWait()
        {
            pthread_cond_wait(&is_empty, &_mtx);
        }
        void WakeupConsumer()
        {
            pthread_cond_signal(&is_empty);
        }
        void WakeupProducter()
        {
            pthread_cond_signal(&is_full);
        }

    public:
        BlockQueue(int cap = default_cap)
            : _cap(cap)
        {
            pthread_mutex_init(&_mtx, nullptr);
            pthread_cond_init(&is_full, nullptr);
            pthread_cond_init(&is_empty, nullptr);
        }
        ~BlockQueue()
        {
            pthread_mutex_destroy(&_mtx);
            pthread_cond_destroy(&is_full);
            pthread_cond_destroy(&is_empty);
        }

    public:
        void Push(const T &in)
        {
            LockQueue();
            //临界区
            //进行条件检测的时候，需要用循环的方式
            //来保证退出循环一定是因为条件不满足而退出的
            while (IsFull())
            {
                //将生产者挂起等待
                ProducterWait();
            }
            //往队列里放数据
            _bq.push(in);
            WakeupConsumer(); //唤醒消费者

            UnlockQueue();
            //唤醒操作在锁里面和外面都一样
            //在里面时，唤醒后所锁还未释放，不过唤醒后生产者会立即执行解锁，消费者一样额能申请到锁
            //在外面时，锁已经释放，消费者被唤醒后可以直接申请锁
            //两种都差不多
            //WakeupConsumer();
        }
        void Pop(T *out)
        {
            LockQueue();
            while (IsEmpty())
            {
                //队列为空，无法消费，让消费者去等待
                ConsumerWait();
            }
            //从队列中拿数据
            *out = _bq.front();
            _bq.pop();
            WakeupProducter(); //唤醒生产者

            UnlockQueue();
        }
    };
}